/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 * $LastChangedRevision$
 */

#include "ble_bondmngr.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf_error.h"
#include "ble_gap.h"
#include "ble_srv_common.h"
#include "ble_sdk_util.h"
#include "nrf_assert.h"
#include "nrf.h"
#include "nrf51_bitfields.h"
#include "ble_flash.h"
#include "ble_bondmngr_cfg.h"


#define FLASH_PAGE                  255                                                 /**< Flash page used for bond manager data. */
#define INVALID_MASTER_HANDLE       -1                                                  /**< Invalid handle, used to indicate that the master is not a known bonded master. */
#define CCCD_SIZE                   6                                                   /**< Number of bytes needed for storing the state of one CCCD. */
#define CRC_SIZE                    2                                                   /**< Size of CRC in sys_attribute data. */
#define SYS_ATTR_BUFFER_MAX_LEN     ((BLE_BONDMNGR_CCCD_COUNT * CCCD_SIZE) + CRC_SIZE)  /**< Size of sys_attribute data. */

#define BONDED_MASTERS_DB_SIZE \
    CEIL_DIV(sizeof(master_data_t) * BLE_BONDMNGR_MAX_BONDED_MASTERS, sizeof(uint32_t)) /**< Size of bonded masters database. */

/**@brief Structure for holding data for one master.
 */
typedef struct
{
    ble_gap_evt_auth_status_t       auth_status;                                        /**< Master authentication data. */
    uint8_t                         sys_attr[SYS_ATTR_BUFFER_MAX_LEN];                  /**< Master sys_attribute data. */
    uint16_t                        sys_attr_size;                                      /**< Master sys_attribute data's size. */
    ble_gap_evt_sec_info_request_t  master_id_info;                                     /**< Master identification info. */
    int8_t                          master_handle;                                      /**< Master's handle. */
} master_data_t;

static uint16_t                     s_conn_handle;                                      /**< Current connection handle. */
static master_data_t                s_master;                                           /**< Current master data. */
static uint32_t                     s_bonded_masters_db[BONDED_MASTERS_DB_SIZE];        /**< Bonded masters database (aligned to a 4 byte boundry). */
static uint8_t                      s_bonded_masters_count;                             /**< Number of bonded masters. */
static master_data_t *              p_bonded_masters_db;                                /**< Pointer to start of bonded masters database. */


static uint32_t master_add(void)
{
    uint32_t err_code;
    
    if (s_master.master_handle == INVALID_MASTER_HANDLE)
    {
        if (s_bonded_masters_count < BLE_BONDMNGR_MAX_BONDED_MASTERS)
        {
            s_master.master_handle = s_bonded_masters_count;
            p_bonded_masters_db[s_bonded_masters_count] = s_master;
            s_bonded_masters_count++;
            err_code = NRF_SUCCESS;
        }
        else
        {
            err_code = NRF_ERROR_NO_MEM;
        }
    }
    else
    {
        if ((s_master.master_handle >= 0) && (s_master.master_handle < s_bonded_masters_count))
        {
            p_bonded_masters_db[s_master.master_handle] = s_master;
            err_code = NRF_SUCCESS;
        }
        else
        {
            err_code = NRF_ERROR_INVALID_PARAM;
        }
    }
    
    return err_code;
}


static uint32_t master_find(const uint16_t master_div)
{
    int i;
    
    s_master.master_handle = INVALID_MASTER_HANDLE;
    for (i = 0; i < s_bonded_masters_count; i++)
    {
        if (master_div == p_bonded_masters_db[i].master_id_info.div)
        {
            s_master = p_bonded_masters_db[i];
            return NRF_SUCCESS;
        }
    }
    
    return NRF_ERROR_NOT_FOUND;
}


void ble_bondmngr_master_delete(void)
{
    ble_flash_init(sizeof(master_data_t));
    ble_flash_page_erase(FLASH_PAGE);
}


static uint32_t on_connect(ble_evt_t * p_ble_evt)
{
    s_conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
    return NRF_SUCCESS;
}


static uint32_t on_disconnect(ble_evt_t * p_ble_evt)
{
    uint32_t err_code;

    err_code = ble_gatts_sys_attr_get(s_conn_handle, s_master.sys_attr, &s_master.sys_attr_size); 
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }
    
    err_code               = master_add();
    s_conn_handle          = BLE_CONN_HANDLE_INVALID;
    s_master.master_handle = INVALID_MASTER_HANDLE;

    return err_code;
}


static uint32_t on_sys_attr_missing(ble_evt_t * p_ble_evt)
{
    if (s_master.master_handle == INVALID_MASTER_HANDLE)
    {
        return ble_gatts_sys_attr_set(s_conn_handle, NULL, 0);
    }
    else
    {
        // Current master is valid, use its data. Set the corresponding sys_attr.
        return ble_gatts_sys_attr_set(s_conn_handle, s_master.sys_attr, s_master.sys_attr_size);
    }
}


static uint32_t on_auth_status(ble_gap_evt_auth_status_t * p_auth_status)
{
    uint32_t err_code = NRF_SUCCESS;

    if (s_master.master_handle == INVALID_MASTER_HANDLE)
    {
        err_code = master_find(p_auth_status->periph_keys.enc_info.div);
        if (err_code == NRF_SUCCESS)
        {
            // Master found in the list of bonded master.
            // Set the corresponding sys_attr.
            err_code = ble_gatts_sys_attr_set(s_conn_handle, 
                                              s_master.sys_attr, 
                                              s_master.sys_attr_size);
        }
        if (err_code == NRF_ERROR_NOT_FOUND)
        {
            err_code = NRF_SUCCESS;
        }
    }
    
    s_master.auth_status        = *p_auth_status;
    s_master.master_id_info.div = p_auth_status->periph_keys.enc_info.div;
    
    return err_code;
}


static uint32_t on_sec_info_request(ble_evt_t * p_ble_evt)
{
    uint32_t err_code;

    s_master.master_id_info = p_ble_evt->evt.gap_evt.params.sec_info_request;
    
    err_code = master_find(s_master.master_id_info.div);
    if (err_code == NRF_SUCCESS)
    {
        // Master found in the list of bonded master. Use the encryption info for this master.
        err_code = ble_gap_sec_info_reply(s_conn_handle, 
                                          &s_master.auth_status.periph_keys.enc_info, 
                                          NULL);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        
        // In addition set the corresponding sys_attr
        err_code = ble_gatts_sys_attr_set(s_conn_handle, s_master.sys_attr, s_master.sys_attr_size);
    }
    else if (err_code == NRF_ERROR_NOT_FOUND)
    {
        // New master
        err_code = ble_gap_sec_info_reply(s_conn_handle, NULL, NULL);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        
        // Initialize the sys_attr
        err_code = ble_gatts_sys_attr_set(s_conn_handle, NULL, 0);
    }
    
    return err_code;
}


uint32_t ble_bondmngr_on_goto_system_off(void)
{
    uint32_t err_code = NRF_SUCCESS;

    if (s_bonded_masters_count != 0)
    {
        err_code = ble_flash_write(FLASH_PAGE, s_bonded_masters_db, s_bonded_masters_count);
    }
    
    return err_code;
}


uint32_t ble_bondmngr_init(void)
{
    int      i;
    uint32_t err_code;
   
    memset(&s_master, 0, sizeof(master_data_t));
    
    s_master.sys_attr_size = SYS_ATTR_BUFFER_MAX_LEN;
    s_master.master_handle = INVALID_MASTER_HANDLE;
    s_conn_handle          = BLE_CONN_HANDLE_INVALID;
    p_bonded_masters_db    = (master_data_t *)(s_bonded_masters_db);

    ble_flash_init(sizeof(master_data_t));
    
    err_code = ble_flash_read(FLASH_PAGE, s_bonded_masters_db, &s_bonded_masters_count);
    if (err_code == NRF_ERROR_NOT_FOUND)
    {
        // The flash does not contain any memorized masters, initialize the RAM DB with default.
        s_bonded_masters_count = 0;
        for (i = 0; i < BLE_BONDMNGR_MAX_BONDED_MASTERS; i++)
        {
            p_bonded_masters_db[i].master_handle = INVALID_MASTER_HANDLE;
        }
        err_code = NRF_SUCCESS;
    }
    
    return err_code;
}


uint32_t ble_bondmngr_on_ble_evt(ble_evt_t * p_ble_evt)
{
    uint32_t err_code = NRF_SUCCESS;
    
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            err_code = on_connect(p_ble_evt);
            break;
            
        case BLE_GAP_EVT_DISCONNECTED:
            err_code = on_disconnect(p_ble_evt);
            break;
            
        case BLE_GATTS_EVT_SYS_ATTR_MISSING:
            err_code = on_sys_attr_missing(p_ble_evt);
            break;

        case BLE_GAP_EVT_AUTH_STATUS:
            err_code = on_auth_status(&p_ble_evt->evt.gap_evt.params.auth_status);
            break;
            
        case BLE_GAP_EVT_SEC_INFO_REQUEST:
            err_code = on_sec_info_request(p_ble_evt);
            break;
            
        default:
            break;
    }
    
    return err_code;
}
